<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>

                    <h4>序列化</h4>
                    <div class="x-wiki-info"><span>984次阅读</span></div>
                    <hr style="border-top-color:#ccc" />
                    <div class="x-wiki-content x-content"><p>在程序运行的过程中，所有的变量都是在内存中，比如，定义一个dict：</p>
<pre><code>d = dict(name=&#39;Bob&#39;, age=20, score=88)
</code></pre><p>可以随时修改变量，比如把<code>name</code>改成<code>&#39;Bill&#39;</code>，但是一旦程序结束，变量所占用的内存就被操作系统全部回收。如果没有把修改后的<code>&#39;Bill&#39;</code>存储到磁盘上，下次重新运行程序，变量又被初始化为<code>&#39;Bob&#39;</code>。</p>
<p>我们把变量从内存中变成可存储或传输的过程称之为序列化，在Python中叫pickling，在其他语言中也被称之为serialization，marshalling，flattening等等，都是一个意思。</p>
<p>序列化之后，就可以把序列化后的内容写入磁盘，或者通过网络传输到别的机器上。</p>
<p>反过来，把变量内容从序列化的对象重新读到内存里称之为反序列化，即unpickling。</p>
<p>Python提供两个模块来实现序列化：<code>cPickle</code>和<code>pickle</code>。这两个模块功能是一样的，区别在于<code>cPickle</code>是C语言写的，速度快，<code>pickle</code>是纯Python写的，速度慢，跟<code>cStringIO</code>和<code>StringIO</code>一个道理。用的时候，先尝试导入<code>cPickle</code>，如果失败，再导入<code>pickle</code>：</p>
<pre><code>try:
    import cPickle as pickle
except ImportError:
    import pickle
</code></pre><p>首先，我们尝试把一个对象序列化并写入文件：</p>
<pre><code>&gt;&gt;&gt; d = dict(name=&#39;Bob&#39;, age=20, score=88)
&gt;&gt;&gt; pickle.dumps(d)
&quot;(dp0\nS&#39;age&#39;\np1\nI20\nsS&#39;score&#39;\np2\nI88\nsS&#39;name&#39;\np3\nS&#39;Bob&#39;\np4\ns.&quot;
</code></pre><p><code>pickle.dumps()</code>方法把任意对象序列化成一个str，然后，就可以把这个str写入文件。或者用另一个方法<code>pickle.dump()</code>直接把对象序列化后写入一个file-like Object：</p>
<pre><code>&gt;&gt;&gt; f = open(&#39;dump.txt&#39;, &#39;wb&#39;)
&gt;&gt;&gt; pickle.dump(d, f)
&gt;&gt;&gt; f.close()
</code></pre><p>看看写入的<code>dump.txt</code>文件，一堆乱七八糟的内容，这些都是Python保存的对象内部信息。</p>
<p>当我们要把对象从磁盘读到内存时，可以先把内容读到一个<code>str</code>，然后用<code>pickle.loads()</code>方法反序列化出对象，也可以直接用<code>pickle.load()</code>方法从一个<code>file-like Object</code>中直接反序列化出对象。我们打开另一个Python命令行来反序列化刚才保存的对象：</p>
<pre><code>&gt;&gt;&gt; f = open(&#39;dump.txt&#39;, &#39;rb&#39;)
&gt;&gt;&gt; d = pickle.load(f)
&gt;&gt;&gt; f.close()
&gt;&gt;&gt; d
{&#39;age&#39;: 20, &#39;score&#39;: 88, &#39;name&#39;: &#39;Bob&#39;}
</code></pre><p>变量的内容又回来了！</p>
<p>当然，这个变量和原来的变量是完全不相干的对象，它们只是内容相同而已。</p>
<p>Pickle的问题和所有其他编程语言特有的序列化问题一样，就是它只能用于Python，并且可能不同版本的Python彼此都不兼容，因此，只能用Pickle保存那些不重要的数据，不能成功地反序列化也没关系。</p>
<h3 id="json">JSON</h3>
<p>如果我们要在不同的编程语言之间传递对象，就必须把对象序列化为标准格式，比如XML，但更好的方法是序列化为JSON，因为JSON表示出来就是一个字符串，可以被所有语言读取，也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式，并且比XML更快，而且可以直接在Web页面中读取，非常方便。</p>
<p>JSON表示的对象就是标准的JavaScript语言的对象，JSON和Python内置的数据类型对应如下：</p>
<table class="table">
<tr>
<td>JSON类型</td>
<td>Python类型</td>
</tr>
<tr>
<td>{}</td>
<td>dict</td>
</tr>
<tr>
<td>[]</td>
<td>list</td>
</tr>
<tr>
<td>&quot;string&quot;</td>
<td>&#39;str&#39;或u&#39;unicode&#39;</td>
</tr>
<tr>
<td>1234.56</td>
<td>int或float</td>
</tr>
<tr>
<td>true/false</td>
<td>True/False</td>
</tr>
<tr>
<td>null</td>
<td>None</td>
</tr>
</table>

<p>Python内置的<code>json</code>模块提供了非常完善的Python对象到JSON格式的转换。我们先看看如何把Python对象变成一个JSON：</p>
<pre><code>&gt;&gt;&gt; import json
&gt;&gt;&gt; d = dict(name=&#39;Bob&#39;, age=20, score=88)
&gt;&gt;&gt; json.dumps(d)
&#39;{&quot;age&quot;: 20, &quot;score&quot;: 88, &quot;name&quot;: &quot;Bob&quot;}&#39;
</code></pre><p><code>dumps()</code>方法返回一个<code>str</code>，内容就是标准的JSON。类似的，<code>dump()</code>方法可以直接把JSON写入一个<code>file-like Object</code>。</p>
<p>要把JSON反序列化为Python对象，用<code>loads()</code>或者对应的<code>load()</code>方法，前者把JSON的字符串反序列化，后者从<code>file-like Object</code>中读取字符串并反序列化：</p>
<pre><code>&gt;&gt;&gt; json_str = &#39;{&quot;age&quot;: 20, &quot;score&quot;: 88, &quot;name&quot;: &quot;Bob&quot;}&#39;
&gt;&gt;&gt; json.loads(json_str)
{u&#39;age&#39;: 20, u&#39;score&#39;: 88, u&#39;name&#39;: u&#39;Bob&#39;}
</code></pre><p>有一点需要注意，就是反序列化得到的所有字符串对象默认都是<code>unicode</code>而不是<code>str</code>。由于JSON标准规定JSON编码是UTF-8，所以我们总是能正确地在Python的<code>str</code>或<code>unicode</code>与JSON的字符串之间转换。</p>
<h3 id="json-">JSON进阶</h3>
<p>Python的<code>dict</code>对象可以直接序列化为JSON的<code>{}</code>，不过，很多时候，我们更喜欢用<code>class</code>表示对象，比如定义<code>Student</code>类，然后序列化：</p>
<pre><code>import json

class Student(object):
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.score = score

s = Student(&#39;Bob&#39;, 20, 88)
print(json.dumps(s))
</code></pre><p>运行代码，毫不留情地得到一个<code>TypeError</code>：</p>
<pre><code>Traceback (most recent call last):
  ...
TypeError: &lt;__main__.Student object at 0x10aabef50&gt; is not JSON serializable
</code></pre><p>错误的原因是<code>Student</code>对象不是一个可序列化为JSON的对象。</p>
<p>如果连<code>class</code>的实例对象都无法序列化为JSON，这肯定不合理！</p>
<p>别急，我们仔细看看<code>dumps()</code>方法的参数列表，可以发现，除了第一个必须的<code>obj</code>参数外，<code>dumps()</code>方法还提供了一大堆的可选参数：</p>
<p><a href="https://docs.python.org/2/library/json.html#json.dumps">https://docs.python.org/2/library/json.html#json.dumps</a></p>
<p>这些可选参数就是让我们来定制JSON序列化。前面的代码之所以无法把<code>Student</code>类实例序列化为JSON，是因为默认情况下，<code>dumps()</code>方法不知道如何将<code>Student</code>实例变为一个JSON的<code>{}</code>对象。</p>
<p>可选参数<code>default</code>就是把任意一个对象变成一个可序列为JSON的对象，我们只需要为<code>Student</code>专门写一个转换函数，再把函数传进去即可：</p>
<pre><code>def student2dict(std):
    return {
        &#39;name&#39;: std.name,
        &#39;age&#39;: std.age,
        &#39;score&#39;: std.score
    }

print(json.dumps(s, default=student2dict))
</code></pre><p>这样，<code>Student</code>实例首先被<code>student2dict()</code>函数转换成<code>dict</code>，然后再被顺利序列化为JSON。</p>
<p>不过，下次如果遇到一个<code>Teacher</code>类的实例，照样无法序列化为JSON。我们可以偷个懒，把任意<code>class</code>的实例变为<code>dict</code>：</p>
<pre><code>print(json.dumps(s, default=lambda obj: obj.__dict__))
</code></pre><p>因为通常<code>class</code>的实例都有一个<code>__dict__</code>属性，它就是一个<code>dict</code>，用来存储实例变量。也有少数例外，比如定义了<code>__slots__</code>的class。</p>
<p>同样的道理，如果我们要把JSON反序列化为一个<code>Student</code>对象实例，<code>loads()</code>方法首先转换出一个<code>dict</code>对象，然后，我们传入的<code>object_hook</code>函数负责把<code>dict</code>转换为<code>Student</code>实例：</p>
<pre><code>def dict2student(d):
    return Student(d[&#39;name&#39;], d[&#39;age&#39;], d[&#39;score&#39;])

json_str = &#39;{&quot;age&quot;: 20, &quot;score&quot;: 88, &quot;name&quot;: &quot;Bob&quot;}&#39;
print(json.loads(json_str, object_hook=dict2student))
</code></pre><p>运行结果如下：</p>
<pre><code>&lt;__main__.Student object at 0x10cd3c190&gt;
</code></pre><p>打印出的是反序列化的<code>Student</code>实例对象。</p>
<h3 id="-">小结</h3>
<p>Python语言特定的序列化模块是<code>pickle</code>，但如果要把序列化搞得更通用、更符合Web标准，就可以使用<code>json</code>模块。</p>
<p><code>json</code>模块的<code>dumps()</code>和<code>loads()</code>函数是定义得非常好的接口的典范。当我们使用时，只需要传入一个必须的参数。但是，当默认的序列化或反序列机制不满足我们的要求时，我们又可以传入更多的参数来定制序列化或反序列化的规则，既做到了接口简单易用，又做到了充分的扩展性和灵活性。</p>
</div>

                    <hr style="border-top-color:#ccc" />

                    